/*
    Copyright (C) 2013  Letian Yi  <letian0805@gmail.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "chat.h"
#define DEF_PORT 8000

static void server_backrun(void);
static int server_init(ChatServer *serv);
static int server_run(ChatServer *serv);
static int server_add_client(ChatServer *serv, ChatClient *cli);
static int server_remove_client(ChatServer *serv, ChatClient *cli);
static void *server_event_thread(void *data);
ChatServer mainserv;

/*
 * Signal management
 *    - SIGUSR1 : dump of all tables
 *    - ...
 */
static void
sig_handler(int sig)
{
    printf("received signal %d\n", sig);
    switch (sig) {
    case SIGUSR1:

        break;
    case SIGHUP:

        break;
    case SIGINT:

        break;
    case SIGTERM:
        {
        ChatList *node = mainserv.cli_list;
        while(node){
            chat_remove_events(node->data);
            close(((ChatClient *)node->data)->sktfd);
            node = node->next;
        }
        exit(0);
        break;
        }
    default:
        break;
    }
}

int main(int argc, char *argv[])
{
    server_backrun();

    signal(SIGINT, sig_handler);
    signal(SIGTERM, sig_handler);
    signal(SIGUSR1, sig_handler);
    signal(SIGHUP, sig_handler);
    signal(SIGPIPE, sig_handler);

    if (server_init(&mainserv)<0){
        return -1;
    }
    if (server_run(&mainserv)<0){
        return -1;
    }
    
    return 0;
}

static void server_backrun(void)
{
    int status;
    if(fork()>0){
        wait(&status);
        exit(0);
    }else if(fork()>0){
        exit(0);
    }
}

static int server_init(ChatServer *serv)
{
    int on = 1;
    struct sockaddr_in serv_addr;
    bzero(serv, sizeof(ChatServer));
    serv->port = DEF_PORT;
    serv->sktfd = socket(AF_INET,SOCK_STREAM,0);
    if (serv->sktfd<0){
        return -1;
    }
    setsockopt(serv->sktfd, SOL_SOCKET, SO_REUSEADDR, (void*)&on, sizeof(on));

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(serv->port);
    if (bind(serv->sktfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr))<0){
        close(serv->sktfd);
        return -1;
    }
    if (listen(serv->sktfd, MAXWAIT)<0){
        close(serv->sktfd);
        return -1;
    }

    return 0;
}

static int server_run(ChatServer *serv)
{
    serv->epfd = epoll_create(MAXEVENTS);
    ChatClient ss;
    bzero(&ss, sizeof(ss));
    ss.epfd = serv->epfd;
    ss.sktfd = serv->sktfd;
    if (chat_add_events(&ss, EPOLLIN) < 0)
        exit(1);

    server_event_thread(serv);

    return 0;
}

static ChatClient *server_find_client(ChatServer *serv, const char *name)
{
    ChatList *node = serv->cli_list;
    while(node){
        ChatClient *cli = node->data;
        if(strcmp(cli->name,name)==0){
            return cli;
        }
        node = node->next;
    }

    return NULL;
}

void sendOnlinelist(ChatServer *serv,ChatClient *myself)
{
    ChatList *node = serv->cli_list;
    while(node && node->data) {
        ChatClient *client = node->data;
        if (client!=myself){
            char str[100];
            snprintf(str,100,CMD_WHOISON_STR"%s", client->name);
            packet_add_msg( myself->pktsnd, str);
        }
        node = node->next;
    }
}


static int server_packet_handler(ChatServer *serv, ChatClient *cli)
{
    int ret = 0;
    ChatPacket *pkt = cli->pktget;
    if (pkt==NULL){
        return -1;
    }
    if (pkt->nmsg<=0){
        ret = -1;
        goto FAILED;
    }
    char *msg = pkt->msg[0];
    ServerCmd cmd = server_parse_cmd(msg);
    switch(cmd){
        case CMD_LOGIN:
        {
            if (pkt->from==NULL){
                ret = -1;
                goto FAILED;
            }
            cli->name = strdup(pkt->from);
            char buf[MAXLEN] = {0};
            sprintf(buf, "hello %s! you can usr 'help' now!\n",cli->name);
            send_to_client(cli, buf);
            printf(" client %s login\n",cli->name);
            ChatList *node = serv->cli_list;
            while(node && node->data) {
                ChatClient *client = node->data;
                client->flag = 1;
                node = node->next;
            }
            break;
        }

    case CMD_GETIELIST: /// runtao
        {
            printf(" client %s check ie list\n", cli->name);
            ChatPacket *pktsnd = packet_new(SERV_NAME, cli->name);
            pktsnd->time = gettime();

#if 0
            ChatList *node = serv->cli_list;
            while(node && node->data) {
                ChatClient *client = node->data;
                if (client!=cli){
                    char str[100];
                    snprintf(str,100,CMD_WHOISON_STR"%s", client->name);
                    packet_add_msg(pktsnd, str);
                }
                node = node->next;
            }
#endif
            cli->pktsnd = pktsnd;
            sendOnlinelist(serv, cli);
            client_flush(cli);
            break;
        }
        case CMD_WHOISON:
        {
            printf(" client %s check who is online\n", cli->name);
            ChatPacket *pktsnd = packet_new(SERV_NAME, cli->name);
            pktsnd->time = gettime();
            ChatList *node = serv->cli_list;
            while(node && node->data){
                ChatClient *client = node->data;
                if (client!=cli){
                    char str[100];
                    snprintf(str,100,CMD_WHOISON_STR"%s", client->name);
                    packet_add_msg(pktsnd, str);
                }
                node = node->next;
            }
            cli->pktsnd = pktsnd;
            client_flush(cli);
            break;
        }
        case CMD_LOGOUT:
            printf(" client %s logout\n",cli->name);
            server_remove_client(serv,cli);
            break;
        default:
            break;
    }

FAILED:
    packet_free(pkt);
    cli->pktget = NULL;
    return ret;
}

static int server_get_msg(ChatServer *serv, ChatClient *cli)
{
    char buf[MAXLEN];
    if(socket_readline(cli->sktfd, buf)<0){
        printf(" read socket error \n");
        server_remove_client(serv,cli);
        return -1;
    }
    char *str = str_strip(buf);
    if (str==NULL){
        return 0;
    }
    if (client_parse_head(cli, str)<0){
        printf(" failed to parse client message\n");
        if (cli->pktget) {
            packet_free(cli->pktget);
            cli->pktget = NULL;
        }
        cli->pktsnd = NULL;
        return -1;
    }
    if (cli->pktstat!=PKT_RDFULL){
        return 0;
    }
    cli->pktstat = PKT_IDLE;

    if (strcmp(cli->pktget->to, SERV_NAME)==0){
        return server_packet_handler(serv, cli);
    }
    ChatClient *tocli = server_find_client(serv, cli->pktget->to);
    if (tocli==NULL){
        return -1;
    }
    tocli->pktsnd = cli->pktget;
    if(tocli->flag) {
        sendOnlinelist(serv, tocli);
        tocli->flag = 0;
    }
    cli->pktget = NULL;
    chat_add_events(tocli, EPOLLOUT);
    return 0;
}

static int server_send_msg(ChatServer *serv, ChatClient *cli)
{
    client_flush(cli);
    chat_remove_events(cli);
    chat_add_events(cli,EPOLLIN);
    return 0;
}

static void *server_event_thread(void *data)
{
    ChatServer *serv = data;
    struct epoll_event evts[MAXEVENTS];
    while(1){
        int fds = epoll_wait(serv->epfd, evts, MAXEVENTS, 100);
        int i;

        for (i=0; i<fds; i++){
            ChatClient *cli = evts[i].data.ptr;
            int evt = (cli->events & evts[i].events);
            if (cli->sktfd == serv->sktfd) {
                if (!evt & EPOLLIN)
                    continue;
                ChatClient *cli1 = (ChatClient *)malloc(sizeof(ChatClient));
                bzero(cli1, sizeof(ChatClient));
                cli1->epfd = serv->epfd;
                cli1->sktfd = accept(serv->sktfd, (struct sockaddr *)&(cli1->sktaddr), &(cli1->sktlen));
                if (cli1->sktfd<0){
                    free(cli1);
                    continue;
                }
                printf("new client connected\n");

                if (chat_add_events(cli1, EPOLLIN)<0){
                    close(cli1->sktfd);
                    free(cli1);
                    continue;
                }
                server_add_client(serv, cli1);
                continue;
            }

            if ( (evt & EPOLLIN) ){
                server_get_msg(serv, cli);
            }else if ((evt & EPOLLOUT) ){
                server_send_msg(serv, cli);
            }
        }
    }

    return NULL;
}

static int server_add_client(ChatServer *serv, ChatClient *cli)
{
    ChatList *node = (ChatList *)malloc(sizeof(ChatList));
    node->data = cli;
    node->next = serv->cli_list;
    node->prev = NULL;
    if (serv->cli_list){
        serv->cli_list->prev = node;
    }
    serv->cli_list = node;
    return 0;
}

static int server_remove_client(ChatServer *serv, ChatClient *cli)
{
    ChatList *node = serv->cli_list;
    chat_remove_events(cli);
    while(node && node->data!=cli){
        node = node->next;
    }
    if (node==NULL){
        return -1;
    }

    if (node==serv->cli_list){
        serv->cli_list = node->next;
        if (serv->cli_list){
            serv->cli_list->prev = NULL;
        }
    }

    if (node->next){
        node->next->prev = node->prev;
    }
    if (node->prev){
        node->prev->next = node->next;
    }

    free(node);

    free(cli->name);
    free(cli);

    return 0;
}

